Chapter 14. Transactions
14.1 ACID TransactionsTo understand how transactions work, we will revisit the TravelAgent EJB, a stateful session bean that encapsulates the process of making a cruise reservation for a customer. In EJB 2.0, the TravelAgent EJB's bookPassage() method looks like this: public TicketDO bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState { if (customer == null || cruise == null || cabin == null) { throw new IncompleteConversationalState(); } try { ReservationHomeLocal resHome = (ReservationHomeLocal) jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal"); ReservationLocal reservation = resHome.create(customer, cruise, cabin, price); Object ref = jndiContext.lookup ("java:comp/env/ejb/ProcessPaymentHomeRemote"); ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class); ProcessPaymentRemote process = ppHome.create(); process.byCredit(customer, card, price); TicketDO ticket = new TicketDO(customer,cruise,cabin,price); return ticket; } catch(Exception e) { throw new EJBException(e); } } In EJB 1.1, the bookPassage() method looks like this: public TicketDO bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState { if (customer == null || cruise == null || cabin == null) { throw new IncompleteConversationalState(); } try { ReservationHomeRemote resHome = (ReservationHomeRemote) getHome("ReservationHomeRemote", ReservationHomeRemote.class); ReservationRemote reservation = resHome.create(customer, cruise, cabin, price); ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) getHome("ProcessPaymentHomeRemote", ProcessPaymentHomeRemote.class); ProcessPaymentRemote process = ppHome.create(); process.byCredit(customer, card, price); TicketDO ticket = new TicketDO(customer,cruise,cabin,price); return ticket; } catch(Exception e) { throw new EJBException(e); } } The TravelAgent EJB is a fairly simple session bean, and its use of other EJBs is a typical example of business-object design and workflow. Unfortunately, good business-object design is not enough to make these EJBs useful in an industrial-strength application. The problem is not with the definition of the EJBs or the workflow; the problem is that a good design does not, in and of itself, guarantee that the TravelAgent EJB's bookPassage() method represents a good transaction. To understand why, we will take a closer look at what a transaction is and what criteria a transaction must meet to be considered reliable. In business, a transaction usually involves an exchange between two parties. When you purchase an ice cream cone, you exchange money for food; when you work for a company, you exchange skill and time for money (which you use to buy more ice cream). When you are involved in these exchanges, you monitor the outcome to ensure that you don't get "ripped off." If you give the ice cream vendor a $20 bill, you don't want him to drive off without giving you your change; likewise, you want to make sure that your paycheck reflects all the hours you worked. By monitoring these commercial exchanges, you are attempting to ensure the reliability of the transactions; you are making sure that each transaction meets everyone's expectations. In business software, a transaction embodies the concept of a commercial exchange. A business system transaction (transaction for short) is the execution of a unit-of-work that accesses one or more shared resources, usually databases. A unit-of-work is a set of activities that relate to each other and must be completed together. The reservation process is a unit-of-work made up of several activities: recording a reservation, debiting a credit card, and generating a ticket together make up a unit-of-work. Transactions are part of many different types of systems. In each transaction, the objective is the same: to execute a unit-of-work that results in a reliable exchange. Here are some examples of other types of business systems that employ transactions:
As you can see, transactions are often complex and usually involve the manipulation of a lot of data. Mistakes in data can cost money, or even a life. Transactions must therefore preserve data integrity, which means that the transaction must work perfectly every time or not be executed at all. This is a pretty tall order, especially for complex systems. As difficult as this requirement is, however, when it comes to commerce there is no room for error. Units-of-work involving money or anything of value always require the utmost reliability, because errors impact the revenues and the well-being of the parties involved. To give you an idea of the accuracy required by transactions, think about what would happen if a transactional system suffered from seemingly infrequent errors. ATMs provide customers with convenient access to their bank accounts and represent a significant percentage of the total transactions in personal banking. The transactions handled by ATMs are simple but numerous, providing us with a great example of why transactions must be error-proof. Let's say that a bank has 100 ATMs in a metropolitan area, and each ATM processes 300 transactions (deposits, withdrawals, or transfers) a day, for a total of 30,000 transactions per day. If each transaction, on average, involves the deposit, withdrawal, or transfer of about $100, about three million dollars will move through the ATM system per day. In the course of a year, that's a little over a billion dollars:
How well do the ATMs have to perform to be considered reliable? For the sake of argument, let's say that ATMs execute transactions correctly 99.99% of the time. This seems to be more than adequate: after all, only one out of every ten thousand transactions executes incorrectly. But over the course of a year, if you do the math, that could result in over $100,000 in errors!
Obviously, this is an oversimplification of the problem, but it illustrates that even a small percentage of errors is unacceptable in high-volume or mission-critical systems. For this reason, experts in the field of transaction services have identified four characteristics of a transaction that must be met for a system to be considered safe. Transactions must be atomic, consistent, isolated, and durable (ACID)—the four horsemen of transaction services. Here's what each term means:
To get a better idea of what these principles mean, we will examine the TravelAgent EJB in terms of the four ACID properties. 14.1.1 Is the TravelAgent EJB Atomic?Our first measure of the TravelAgent EJB's reliability is its atomicity: does it ensure that the transaction executes completely or not at all? What we are really concerned with are the critical tasks that change or create information. In the bookPassage() method, a Reservation EJB is created, the ProcessPayment EJB debits a credit card, and a TicketDO object is created. All of these tasks must be successful for the entire transaction to be successful. To understand the importance of the atomic characteristic, you have to imagine what would happen if even one of the subtasks failed to execute. If, for example, the creation of a Reservation EJB failed but all other tasks succeeded, your customer would probably end up getting bumped from the cruise or sharing the cabin with a stranger. As far as the travel agent is concerned, the bookPassage() method executed successfully because a TicketDO was generated. If a ticket is generated without the creation of a reservation, the state of the business system becomes inconsistent with reality, because the customer paid for a ticket but the reservation was not recorded. Likewise, if the ProcessPayment EJB fails to charge the customer's credit card, the customer gets a free cruise. He may be happy, but management won't be. Finally, if the TicketDO is never created, the customer will have no record of the transaction and probably will not be allowed onto the ship. So the only way bookPassage() can be completed is if all the critical tasks execute successfully. If something goes wrong, the entire process must be aborted. Aborting a transaction requires more than simply not finishing the tasks; in addition, all the tasks that did execute within the transaction must be undone. If, for example, the creation of the Reservation EJB and ProcessPayment.byCredit() method succeeded but the creation of the TicketDO failed, throwing an exception from the constructor, the reservation record and payment records must not be added to the database. 14.1.2 Is the TravelAgent EJB Consistent?In order for a transaction to be consistent, the business system must make sense after the transaction has completed. In other words, the state of the business system must be consistent with the reality of the business. This requires that the transaction enforce the atomic, isolated, and durable characteristics of the transaction, and it also requires diligent enforcement of integrity constraints by the application developer. If, for example, the application developer fails to include the credit card charge operation in the bookPassage() method, the customer will be issued a ticket but will never be charged. The data will be inconsistent with the expectation of the business—a customer should be charged for passage. In addition, the database must be set up to enforce integrity constraints. For example, it should not be possible for a record to be added to the RESERVATION table unless the CABIN_ID, CRUISE_ID, and CUSTOMER_ID foreign keys map to corresponding records in the CABIN, CRUISE, and CUSTOMER tables, respectively. If a CUSTOMER_ID that does not map to a CUSTOMER record is used, referential integrity should cause the database to throw an error message. 14.1.3 Is the TravelAgent EJB Isolated?If you are familiar with the concept of thread synchronization in Java or row-locking schemes in relational databases, isolation will be a familiar concept. To be isolated, a transaction must protect the data it is accessing from other transactions. This is necessary to prevent other transactions from interacting with data that is in transition. In the TravelAgent EJB, the transaction is isolated to prevent other transactions from modifying the EJBs that are being updated. Imagine the problems that would arise if separate transactions were allowed to change any entity bean at any time—transactions would walk all over each other. You could easily have several customers book the same cabin because their travel agents happened to make their reservations at the same time. The isolation of data accessed by EJBs does not mean that the entire application shuts down during a transaction. Only those entity beans and data directly affected by the transaction are isolated. In the TravelAgent EJB, for example, the transaction isolates only the Reservation EJB created. There can be many Reservation EJBs in existence; there's no reason these other EJBs can't be accessed by other transactions. 14.1.4 Is the TravelAgent EJB Durable?To be durable, the bookPassage() method must write all changes and new data to a permanent data store before it can be considered successful. While this may seem like a no-brainer, often it is not what happens in real life. In the name of efficiency, changes are often maintained in memory for long periods of time before being saved on a disk drive. The idea is to reduce disk accesses—which slow systems down—and only periodically write the cumulative effect of data changes. While this approach is great for performance, it is also dangerous because data can be lost when the system goes down and memory is wiped out. Durability requires the system to save all updates made within a transaction as the transaction successfully completes, thus protecting the integrity of the data. In the TravelAgent EJB, this means that the new RESERVATION and PAYMENT records inserted are made persistent before the transaction can complete successfully. Only when the data is made durable are those specific records accessible through their respective EJBs from other transactions. Hence, durability also plays a role in isolation. A transaction is not finished until the data is successfully recorded. Ensuring that transactions adhere to the ACID principles requires careful design. The system has to monitor the progress of a transaction to ensure that it does all its work, that the data is changed correctly, that transactions do not interfere with each other, and that the changes can survive a system crash. Engineering all this functionality into a system is a lot of work, and not something you would want to reinvent for every business system on which you work. Fortunately, EJB is designed to support transactions automatically, making the development of transactional systems easier. The rest of this chapter examines how EJB supports transactions implicitly (through declarative transaction attributes) and explicitly (through the Java Transaction API). 14.2 Declarative Transaction ManagementOne of the primary advantages of Enterprise JavaBeans is that it allows for declarative transaction management. Without this feature, transactions must be controlled using explicit transaction demarcation. This involves the use of fairly complex APIs like the OMG's Object Transaction Service (OTS) or its Java implementation, the Java Transaction Service ( JTS). Explicit demarcation is difficult for developers to use at best, particularly if you are new to transactional systems. In addition, explicit transaction demarcation requires that the transactional code be written within the business logic, which reduces the clarity of the code and, more importantly, creates inflexible distributed objects. Once transaction demarcation is hardcoded into the business object, changes in transaction behavior require changes to the business logic itself. We talk more about explicit transaction management and EJB later in this chapter. With declarative transaction management, the transactional behavior of EJBs can be controlled using the deployment descriptor, which sets transaction attributes for individual enterprise bean methods. This means that the transactional behavior of an EJB can be changed without changing the EJB's business logic. In addition, an EJB deployed in one application can be defined with different transactional behavior than the same EJB deployed in a different application. Declarative transaction management reduces the complexity of transactions for EJB developers and application developers and makes it easier to create robust transactional applications. 14.2.1 Transaction ScopeTransaction scope is a crucial concept for understanding transactions. In this context, transaction scope means those EJBs—both session and entity—that are participating in a particular transaction. In the bookPassage() method of the TravelAgent EJB, all the EJBs involved are part of the same transaction scope. The scope of the transaction starts when a client invokes the TravelAgent EJB's bookPassage() method. Once the transaction scope has started, it is propagated to both the newly created Reservation EJB and the ProcessPayment EJB. As you know, a transaction is a unit-of-work made up of one or more tasks. In a transaction, all the tasks that make up the unit-of-work must succeed for the entire transaction to succeed; the transaction must be atomic. If any task fails, the updates made by all the other tasks in the transaction will be rolled back or undone. In EJB, tasks are expressed as enterprise bean methods, and a unit-of-work consists of every enterprise bean method invoked in a transaction. The scope of a transaction includes every EJB that participates in the unit-of-work. It is easy to trace the scope of a transaction by following the thread of execution. If the invocation of the bookPassage() method begins a transaction, then logically, the transaction ends when the method completes. The scope of the bookPassage() transaction would include the TravelAgent, Reservation, and ProcessPayment EJBs—every EJB touched by the bookPassage() method. A transaction is propagated to an EJB when that EJB's method is invoked and included in the scope of that transaction. A transaction can end if an exception is thrown while the bookPassage() method is executing. The exception can be thrown from one of the other EJBs or from the bookPassage() method itself. An exception may or may not cause a rollback, depending on its type. We'll discuss exceptions and transactions in more detail later. The thread of execution is not the only factor that determines whether an EJB is included in the scope of a transaction; the EJB's transaction attributes also play a role. Determining whether an EJB participates in the transaction scope of any unit-of-work is accomplished either implicitly using the EJB's transaction attributes or explicitly using the Java Transaction API ( JTA). 14.2.2 Transaction AttributesAs an application developer, you do not normally need to control transactions explicitly when using an EJB server. EJB servers can manage transactions implicitly, based on the transaction attributes established for EJBs at deployment time. The ability to specify how business objects participate in transactions through attribute-based programming is a common characteristic of CTMs, and one of the most important features of the EJB component model. When an EJB is deployed, you can set its runtime transaction attribute in the deployment descriptor to one of several values. The following list shows the XML attribute values used to specify these transaction attributes:
Using transaction attributes simplifies building transactional applications by reducing the risks associated with improper use of transactional protocols such as JTA (discussed later in this chapter). It's more efficient and easier to use transaction attributes than to control transactions explicitly. You can set a transaction attribute for the entire EJB (in which case it applies to all methods) or to set different transaction attributes for individual methods. The former method is much simpler and less error prone, but setting attributes at the method level offers more flexibility. The code fragments in the following sections show how to set the default transaction attribute of an EJB in the EJB's deployment descriptor. 14.2.2.1 Setting a transaction attributeIn the XML deployment descriptor, a <container-transaction> element specifies the transaction attributes for the EJBs described in the deployment descriptor: <ejb-jar> ... <assembly-descriptor> ... <container-transaction> <method> <ejb-name>TravelAgentEJB</ejb-name> <method-name> * </method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>TravelAgentEJB</ejb-name> <method-name>listAvailableCabins</method-name> </method> <trans-attribute>Supports</trans-attribute> </container-transaction> ... </assembly-descriptor> ... </ejb-jar> This deployment descriptor specifies the transaction attributes for the TravelAgent EJB. Each <container-transaction> element specifies a method and the transaction attribute that should be applied to that method. The first <container-transaction> element specifies that all methods by default have a transaction attribute of Required; the * is a wildcard that indicates all the methods of the TravelAgent EJB. The second <container-transaction> element overrides the default setting to specify that the listAvailableCabins() method will have a Supports transaction attribute. Note that we have to specify which EJB we are referring to with the <ejb-name> element; an XML deployment descriptor can cover many EJBs. 14.2.2.2 Transaction attributes definedHere are the definitions of the transaction attributes listed earlier. In a few of the definitions, we say that the client transaction is suspended . This means that the transaction is not propagated to the enterprise bean method being invoked; propagation of the transaction is temporarily halted until the enterprise bean method returns. To make things easier, we will talk about attribute types as if they were bean types: for example, we'll say a "Required EJB" as shorthand for "an enterprise bean with the Required transaction attribute." The attributes are:
Figure 14-1. NotSupported attribute
Figure 14-2. Supports attribute
Figure 14-3. Required attribute
Figure 14-4. RequiresNew attribute
Figure 14-5. Mandatory attribute
Figure 14-6. Never attribute
14.2.2.3 EJB 2.0: Container-managed persistence and transaction attributesThe EJB 2.0 specification strongly advises that CMP 2.0 entity beans use only the Required, RequiresNew, and Mandatory transaction attributes. This restriction ensures that all database access occurs in the context of a transaction, which is important when the container is automatically managing persistence. While the specification requires that these three transaction attributes be supported for CMP 2.0, support for the Never, Supports, and NotSupported transaction attributes is optional. If a vendor wishes to support these attributes (which allow the bean to execute without a transaction) they may do so, but it's not recommended. Consult your vendor's documentation to determine if they support the optional transaction attributes. This book recommends that you use only Required, RequiresNew, or Mandatory with EJB 2.0 container-managed persistence entity beans. 14.2.2.4 EJB 2.0: Message-driven beans and transaction attributesMessage-driven beans may declare only the NotSupported or Required transaction attributes. The other transaction attributes don't make sense in message-driven beans because they apply to client-initiated transactions. The Supports, RequiresNew, Mandatory, and Never attributes are all relative to the transaction context of the client. For example, the Mandatory attribute requires the client to have a transaction in progress before calling the enterprise bean. This is meaningless for a message-driven bean, which is decoupled from the client. The NotSupported transaction attribute indicates that the message will be processed without a transaction. The Required transaction attribute indicates that the message will be processed with a container-initiated transaction. 14.2.3 Transaction PropagationTo illustrate the impact of transaction attributes on enterprise bean methods, we'll look once again at the bookPassage() method of the TravelAgent EJB created in Chapter 12 (see the listings earlier in this chapter). In order for bookPassage() to execute as a successful transaction, both the creation of the Reservation EJB and the charge to the customer must be successful. This means that both operations must be included in the same transaction. If either operation fails, the entire transaction fails. We could have specified the Required transaction attribute as the default for all the EJBs involved, because that attribute enforces our desired policy that all EJBs must execute within a transaction and thus ensures data consistency. As a transaction monitor, an EJB server watches each method call in the transaction. If any of the updates fail, all the updates to all the EJBs will be reversed or rolled back. A rollback is like an undo command. If you have worked with relational databases, the concept of a rollback should be familiar to you. Once an update is executed, you can either commit the update or roll it back. A commit makes the changes requested by the update permanent; a rollback aborts the update and leaves the database in its original state. Making EJBs transactional provides the same kind of rollback/commit control. For example, if the Reservation EJB cannot be created, the charge made by the ProcessPayment EJB is rolled back. Transactions make updates an all-or-nothing proposition. This ensures that the unit-of-work, like the bookPassage() method, executes as intended, and it prevents inconsistent data from being written to databases. In cases in which the container implicitly manages the transaction, the commit and rollback decisions are handled automatically. When transactions are managed explicitly within an enterprise bean or by the client, the responsibility falls on the enterprise bean or application developer to commit or roll back a transaction. Explicit demarcation of transactions is covered in detail later in this chapter. Let's assume that the TravelAgent EJB is created and used on a client as follows: TravelAgent agent = agentHome.create(customer); agent.setCabinID(cabin_id); agent.setCruiseID(cruise_id); try { agent.bookPassage(card,price); } catch(Exception e) { System.out.println("Transaction failed!"); } Furthermore, let's assume that the bookPassage() method has been given the transaction attribute RequiresNew. In this case, the client that invokes the bookPassage() method is not itself part of a transaction. When bookPassage() is invoked on the TravelAgent EJB, a new transaction is created, as dictated by the RequiresNew attribute. This means that the TravelAgent EJB registers itself with the EJB server's transaction manager, which will manage the transaction automatically. The transaction manager coordinates transactions, propagating the transaction scope from one EJB to the next to ensure that all EJBs touched by a transaction are included in the transaction's unit-of-work. That way, the transaction manager can monitor the updates made by each enterprise bean and decide, based on the success of those updates, whether to commit all changes made by all enterprise beans to the database or roll them all back. If a system exception is thrown by the bookPassage() method, the transaction is automatically rolled back. We will talk more about exceptions later in this chapter. When the byCredit() method is invoked within the bookPassage() method, the ProcessPayment EJB registers with the transaction manager under the transactional context that was created for the TravelAgent EJB; the transactional context is propagated to the ProcessPayment EJB. When the new Reservation EJB is created, it is also registered with the transaction manager under the same transaction. When all the EJBs are registered and their updates are made, the transaction manager checks to ensure that their updates will work. If all the updates will work, the transaction manager allows the changes to become permanent. If one of the EJBs reports an error or fails, any changes made by either the ProcessPayment or Reservation EJB are rolled back by the transaction manager. Figure 14-7 illustrates the propagation and management of the TravelAgent EJB's transactional context. Figure 14-7. Managing the TravelAgent EJB's transactional context
In addition to managing transactions in its own environment, an EJB server can coordinate with other transactional systems. If, for example, the ProcessPayment EJB actually came from a different EJB server than the TravelAgent EJB, the two EJB servers would cooperate to manage the transaction as one unit-of-work. This is called a distributed transaction.[1] A distributed transaction is a great deal more complicated, requiring what is called a two-phase commit (2-PC or TPC). 2-PC is a mechanism that allows transactions to be managed across different servers and resources (e.g., databases and JMS providers). The details of a 2-PC are beyond the scope of this book, but a system that supports it will not require any extra operations by an EJB or application developer. If distributed transactions are supported, the protocol for propagating transactions, as discussed earlier, will be supported. In other words, as an application or EJB developer, you should not notice a difference between local and distributed transactions. 14.2.4 EJB 2.0: Collection-Based Relationships and TransactionsIn EJB 2.0 container-managed persistence, collection-based relationships may only be accessed within a single transaction. In other words, it's illegal to obtain a Collection object from a collection-based relationship field in one transaction and then use it in another. For example, if an enterprise bean accesses another's collection-based relationship field through its local interface, the Collection returned from the accessor method can be used only within the same transaction: public class HypotheticalBean implements javax.ejb.EntityBean { public void methodX(CustomerLocal customer) { Collection reservations = customer.getReservations(); Iterator iterator = reservations.iterator; while(iterator.hasNext()){ ... ... } ... } If the Customer EJB's getReservations() method was declared with a transaction attribute of RequiresNew, attempting to invoke any methods on the Collection, including the iterator() method, will result in a java.lang.IllegalStateException. This exception is thrown because the Collection object was created within the scope of the getReservations() transaction, not in the scope of methodX()'s transaction. The transaction context of methodX() is different from the transaction context of the getReservations() method. The Collection from an entity bean can be used by another co-located bean only if it is obtained and accessed in the same transaction context. As long as the Customer EJB's getReservations() method propagates the transaction context of methodX(), the Collection can be used without any problems. This can be accomplished by changing the getReservations() method so that it declares its transaction attribute as Required or Mandatory. 14.3 Isolation and Database LockingTransaction isolation (the "I" in ACID) is a critical part of any transactional system. This section explains isolation conditions, database locking, and transaction isolation levels. These concepts are important when deploying any transactional system. 14.3.1 Dirty, Repeatable, and Phantom ReadsTransaction isolation is defined in terms of isolation conditions called dirty reads, repeatable reads, and phantom reads. These conditions describe what can happen when two or more transactions operate on the same data.[2] To illustrate these conditions, let's think about two separate client applications using their own instances of the TravelAgent EJB to access the same data—specifically, a cabin record with the primary key of 99. These examples revolve around the RESERVATION table, which is accessed by both the bookPassage() method (through the Reservation EJB) and the listAvailableCabins() method (through JDBC). It might be a good idea to go back to Chapter 12 and review how the RESERVATION table is accessed through these methods. This will help you to understand how two transactions executed by two different clients can impact each other. Assume that both methods have a transaction attribute of Required. 14.3.1.1 Dirty readsA dirty read occurs when the first transaction reads uncommitted changes made by a second transaction. If the second transaction is rolled back, the data read by the first transaction becomes invalid because the rollback undoes the changes. The first transaction will not be aware that the data it has read has become invalid. Here's a scenario showing how a dirty read can occur (illustrated in Figure 14-8):
14.3.1.2 Repeatable readsA repeatable read is when the data read is guaranteed to look the same if read again during the same transaction. Repeatable reads are guaranteed in one of two ways: either the data read is locked against changes or the data read is a snapshot that doesn't reflect changes. If the data is locked, it cannot be changed by any other transaction until the current transaction ends. If the data is a snapshot, other transactions can change the data, but these changes will not be seen by this transaction if the read is repeated. Here's an example of a repeatable read (illustrated in Figure 14-9):
14.3.1.3 Phantom readsPhantom reads occur when new records added to the database are detectable by transactions that started prior to the insert. Queries will include records added by other transactions after their transaction has started. Here's a scenario that includes a phantom read (illustrated in Figure 14-10):
14.3.2 Database LocksDatabases, especially relational databases, normally use several different locking techniques. The most common are read locks, write locks, and exclusive write locks. (I've taken the liberty of adding "snapshots," although this isn't a formal term.) These locking mechanisms control how transactions access data concurrently. Locking mechanisms impact the read conditions I just described. These types of locks are simple concepts that are not directly addressed in the EJB specification. Database vendors implement these locks differently, so you should understand how your database addresses these locking mechanisms to best predict how the isolation levels described in this section will work. The four types of lock are:
14.3.3 Transaction Isolation LevelsTransaction isolation is defined in terms of the isolation conditions (dirty reads, repeatable reads, and phantom reads). Isolation levels are commonly used in database systems to describe how locking is applied to data within a transaction.[3] The following terms are usually used to discuss isolation levels:
These isolation levels are the same as those defined for JDBC. Specifically, they map to the static final variables in the java.sql.Connection class. The behavior modeled by the isolation levels in the connection class is the same as the behavior described here. The exact behavior of these isolation levels depends largely on the locking mechanism used by the underlying database or resource. How the isolation levels work depends in large part on how your database supports them. In EJB, the deployer sets transaction isolation levels in a vendor-specific way if the container manages the transaction. The EJB developer sets the transaction isolation level if the enterprise bean manages its own transactions. Up to this point we have discussed only container-managed transactions; we will discuss bean-managed transactions later in this chapter. 14.3.4 Balancing Performance Against ConsistencyGenerally speaking, as the isolation levels become more restrictive, the performance of the system decreases because more restrictive isolation levels prevent transactions from accessing the same data. If isolation levels are very restrictive, like Serializable, then all transactions, even simple reads, must wait in line to execute. This can result in a system that is very slow. EJB systems that process a large number of concurrent transactions and need to be very fast will therefore avoid the Serializableisolation level where it is not necessary, since it will be prohibitively slow. Isolation levels, however, also enforce consistency of data. More restrictive isolation levels help ensure that invalid data is not used for performing updates. The old adage "garbage in, garbage out" applies here. The Serializable isolation level ensures that data is never accessed concurrently by transactions, thus ensuring that the data is always consistent. Choosing the correct isolation level requires some research about the database you are using and how it handles locking. You must also balance the performance needs of your system against consistency. This is not a cut-and-dried process, because different applications use data differently. Although there are only three ships in Titan's system, the entity beans that represent them are included in most of Titan's transactions. This means that many, possibly hundreds, of transactions will be accessing these Ship EJBs at the same time. Access to Ship EJBs needs to be fast or it becomes a bottleneck, so we do not want to use a restrictive isolation level. At the same time, the ship data also needs to be consistent; otherwise, hundreds of transactions will be using invalid data. Therefore, we need to use a strong isolation level when making changes to ship information. To accommodate these conflicting requirements, we can apply different isolation levels to different methods. Most transactions use the Ship EJB's get methods to obtain information. This is read-only behavior, so the isolation level for the get methods can be very low, such as Read Uncommitted. The set methods of the Ship EJB are almost never used; the name of the ship probably will not change for years. However, the data changed by the set methods must be isolated to prevent dirty reads by other transactions, so we will use the most restrictive isolation level, Serializable, on the ship's set methods. By using different isolation levels on different business methods, we can balance consistency against performance. 14.3.4.1 Controlling isolation levelsDifferent EJB servers allow different levels of granularity for setting isolation levels; some servers defer this responsibility to the database. Most EJB servers control the isolation level through the resource access API (e.g., JDBC and JMS) and may allow different resources to have different isolation levels, but will generally require that access to the same resource within a single transaction use a consistent isolation level. You will need to consult your vendor's documentation to find out the level of control your server offers. Bean-managed transactions in session beans (stateful and stateless) and message-driven beans (EJB 2.0), however, allow the EJB developer to specify the transaction isolation level using the API of the resource providing persistent storage. The JDBC API, for example, provides a mechanism for specifying the isolation level of the database connection. The following code shows how this is done. Bean-managed transactions are covered in more detail later in this chapter. ... DataSource source = (javax.sql.DataSource) jndiCntxt.lookup("java:comp/env/jdbc/titanDB"); Connection con = source.getConnection(); con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); ... You can set the isolation level to be different for different resources within the same transaction, but all enterprise beans that use the same resource in a transaction should use the same isolation level. 14.4 Nontransactional BeansBeans that reside outside a transaction scope normally provide some kind of stateless service that does not directly manipulate data in a data store. While these types of enterprise beans may be necessary as utilities during a transaction, they do not need to meet the stringent ACID requirements of a transaction. Consider a nontransactional stateless session bean, the Quote EJB, that provides live stock quotes. This EJB may respond to a request from a transactional EJB involved in a stock purchase transaction. The success or failure of the stock purchase, as a transaction, will not impact the state or operations of the Quote EJB, so it does not need to be part of the transaction. Beans that are involved in transactions are subjected to the isolated ACID property, which means that their services cannot be shared during the life of the transaction. Making an enterprise bean transactional can be an expensive runtime activity. Declaring an EJB to be nontransactional (i.e., NotSupported) leaves it out of the transaction scope, which may improve the performance and availability of that service. 14.5 Explicit Transaction Management
In EJB, implicit transaction management is provided on the enterprise bean method level so that we can define transactions that are delimited by the scope of the method being executed. This is one of the primary advantages of EJB over cruder distributed object implementations: it reduces complexity and therefore programmer error. In addition, declarative transaction demarcation, as used in EJB, separates the transactional behavior from the business logic; a change to transactional behavior does not require changes to the business logic. In rare situations, however, it may be necessary to take control of transactions explicitly. To do this, it is necessary to have a much more complete understanding of transactions. Explicit management of transactions is complex and is normally accomplished using the OMG's Object Transaction Service (OTS) or the Java implementation of OTS, the Java Transaction Service ( JTS). OTS and JTS provide APIs that allow developers to work with transaction managers and resources (e.g., databases and JMS providers) directly. While the JTS implementation of OTS is robust and complete, it is not the easiest API to work with; it requires clean and intentional control over the bounds of enrollment in transactions. Enterprise JavaBeans supports a much simpler API, the Java Transaction API ( JTA), for working with transactions. This API is implemented by the javax.transaction package. JTA actually consists of two components: a high-level transactional client interface and a low-level X/Open XA interface. We are concerned with the high-level client interface, since that is the one accessible to the enterprise beans and is the recommended transactional interface for client applications. The low-level XA interface is used by the EJB server and container to automatically coordinate transactions with resources such as databases. As an application and EJB developer, your use of explicit transaction management will probably focus on one very simple interface: javax.transaction.UserTransaction. UserTransaction provides an interface to the transaction manager that allows the application developer to manage the scope of a transaction explicitly. Here is an example of how explicit demarcation might be used in an EJB or client application: Object ref = getInitialContext().lookup("TravelAgentHomeRemote"); TravelAgentHome home = (TravelAgentHome) PortableRemoteObject.narrow(ref, TravelAgentHome.class); TravelAgent tr1 = home.create(customer); tr1.setCruiseID(cruiseID); tr1.setCabinID(cabin_1); TravelAgent tr2 = home.create(customer); tr2.setCruiseID(cruiseID); tr2.setCabinID(cabin_2); javax.transaction.UserTransaction tran = ...; // Get the UserTransaction. tran.begin(); tr1.bookPassage(visaCard,price); tr2.bookPassage(visaCard,price); tran.commit(); The client application needs to book two cabins for the same customer—in this case, the customer is purchasing a cabin for himself and his children. The customer does not want to book either cabin unless he can get both, so the client application is designed to include both bookings in the same transaction. Explicitly marking the transaction's boundaries through the use of the javax.transaction.UserTransaction object does this. Each enterprise bean method invoked by the current thread between the UserTransaction.begin() and UserTransaction.commit() methods is included in the same transaction scope, according to the transaction attributes of the enterprise bean methods invoked. Obviously this example is contrived, but the point it makes is clear. Transactions can be controlled directly, instead of depending on method scope to delimit them. The advantage of using explicit transaction demarcation is that it gives the client control over the bounds of a transaction. The client, in this case, may be a client application or another enterprise bean.[4] In either case, the same javax.transaction.UserTransaction is used, but it is obtained from different sources depending on whether it is needed on the client or in an enterprise bean. Java 2 Enterprise Edition ( J2EE) specifies how a client application can obtain a UserTransaction object using JNDI. Here's how a client obtains a UserTransaction object if the EJB container is part of a J2EE system ( J2EE and its relationship with EJB is covered in more detail in Chapter 17): ... Context jndiCntx = new InitialContext(); UserTransaction tran = (UserTransaction) jndiCntx.lookup("java:comp/UserTransaction"); utx.begin(); ... utx.commit(); ... Enterprise beans can also manage transactions explicitly. Only session beans and message-driven beans with the <transaction-type> value of Bean can manage their own transactions. Enterprise beans that manage their own transactions are frequently referred to as bean-managed transaction (BMT) beans. Entity beans can never be BMT beans. BMT beans do not declare transaction attributes for their methods. Here's how a session bean declares that it will manage transactions explicitly: <ejb-jar> <enterprise-beans> ... <session> ... <transaction-type>Bean</transaction-type> ... To manage its own transaction, an enterprise bean needs to obtain a UserTransaction object. An enterprise bean obtains a reference to the UserTransaction from the EJBContext, as shown here: public class HypotheticalBean extends SessionBean { SessionContext ejbContext; public void someMethod() { try { UserTransaction ut = ejbContext.getUserTransaction(); ut.begin(); // Do some work. ut.commit(); } catch(IllegalStateException ise) {...} catch(SystemException se) {...} catch(TransactionRolledbackException tre) {...} catch(HeuristicRollbackException hre) {...} catch(HeuristicMixedException hme) {...} An enterprise bean can also access the UserTransaction from the JNDI ENC, as shown in the following example. Both methods are legal and proper. The enterprise bean performs the lookup using the "java:comp/env/UserTransaction" context: InitialContext jndiCntx = new InitialContext(); UserTransaction tran = (UserTransaction) jndiCntx.lookup("java:comp/env/UserTransaction"); 14.5.1 Transaction Propagation in Bean-Managed TransactionsWith stateless session beans, transactions that are managed using the UserTransaction must be started and completed within the same method. In other words, UserTransaction transactions cannot be started in one method and ended in another. This makes sense because stateless session bean instances are shared across many clients; so while one stateless instance may service a client's first request, a completely different instance may service a subsequent request by the same client. With stateful session beans, however, a transaction can begin in one method and be committed in another because a stateful session bean is used by only one client. This allows a stateful session bean to associate itself with a transaction across several different client-invoked methods. As an example, imagine the TravelAgent EJB as a BMT bean. In the following code, the transaction is started in the setCruiseID() method and completed in the bookPassage() method. This allows the TravelAgent EJB's methods to be associated with the same transaction. 14.5.1.1 EJB 2.0: TravelAgentBeanThe definition of the TravelAgentBean class looks like this in EJB 2.0: import com.titan.reservation.*; import java.sql.*; import javax.sql.DataSource; import java.util.Vector; import java.rmi.RemoteException; import javax.naming.NamingException; import javax.ejb.EJBException; public class TravelAgentBean implements javax.ejb.SessionBean { ... public void setCruiseID(Integer cruiseID) throws javax.ejb.FinderException { try { ejbContext.getUserTransaction().begin(); CruiseHomeLocal home = (CruiseHomeLocal) jndiContext.lookup("java:comp/env/ejb/CruiseHome"); cruise = home.findByPrimaryKey(cruiseID); } catch(RemoteException re) { throw new EJBException(re); } } public TicketDO bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState { try { if (ejbContext.getUserTransaction().getStatus() != javax.transaction.Status.STATUS_ACTIVE) { throw new EJBException("Transaction is not active"); } } catch(javax.transaction.SystemException se) { throw new EJBException(se); } if (customer == null || cruise == null || cabin == null) { throw new IncompleteConversationalState(); } try { ReservationHomeLocal resHome = (ReservationHomeLocal) jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal"); ReservationLocal reservation = resHome.create(customer, cruise, cabin, price); Object ref = jndiContext.lookup("java:comp/env/ejb/ProcessPaymentHomeRemote"); ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class); ProcessPaymentRemote process = ppHome.create(); process.byCredit(customer, card, price); TicketDO ticket = new TicketDO(customer,cruise,cabin,price); ejbContext.getUserTransaction().commit(); return ticket; } catch(Exception e) { throw new EJBException(e); } } ... } 14.5.1.2 EJB 1.1: TravelAgentBeanIn EJB 1.1, the TravelAgentBean class definition looks like this: public class TravelAgentBean implements javax.ejb.SessionBean { ... public void setCruiseID(Integer cruiseID) throws javax.ejb.FinderException { try { ejbContext.getUserTransaction().begin(); CruiseHomeRemote home = (CruiseHomeRemote) getHome("CruiseHome", CruiseHomeRemote. class); cruise = home.findByPrimaryKey(cruiseID); } catch(RemoteException re) { throw new EJBException(re); } } public TicketDO bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState { try { if (ejbContext.getUserTransaction().getStatus() != javax.transaction.Status.STATUS_ACTIVE) { throw new EJBException("Transaction is not active"); } } catch(javax.transaction.SystemException se) { throw new EJBException(se); } if (customer == null || cruise == null || cabin == null) { throw new IncompleteConversationalState(); } try { ReservationHomeRemote resHome = (ReservationHomeRemote) getHome("ReservationHomeRemote", ReservationHomeRemote.class); ReservationRemote reservation = resHome.create(customer, cruise, cabin, price); ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) getHome("ProcessPaymentHomeRemote", ProcessPaymentHomeRemote.class); ProcessPaymentRemote process = ppHome.create(); process.byCredit(customer, card, price); TicketDO ticket = new TicketDO(customer,cruise,cabin,price); ejbContext.getUserTransaction().commit(); return ticket; } catch(Exception e) { throw new EJBException(e); } } ... } Repeated calls to the EJBContext.getUserTransaction() method return a reference to the same UserTransaction object. The container is required to retain the association between the transaction and the stateful bean instance across multiple client calls until the transaction terminates. In the bookPassage() method, we can check the status of the transaction to ensure that it is still active. If the transaction is no longer active, we throw an exception. The use of the getStatus() method is covered in more detail later in this chapter. When a bean-managed transaction method is invoked by a client that is already involved in a transaction, the client's transaction is suspended until the method returns. This suspension occurs regardless of whether the BMT bean explicitly started its own transaction within the method or the transaction was started in a previous method invocation. The client transaction is always suspended until the BMT method returns.
14.5.1.3 EJB 2.0: Message-driven beans and bean-managed transactionsMessage-driven beans also have the option of managing their own transactions. In the case of MDBs, the scope of the transaction must begin and end within the onMessage() method—it is not possible for a bean-managed transaction to span onMessage() calls. You can transform the ReservationProcessor EJB into a BMT bean simply by changing its <transaction-type> value to Bean: <ejb-jar> <enterprise-beans> ... <message-driven> ... <transaction-type>Bean</transaction-type> ... In this case, the ReservationProcessorBean class would be modified to use the javax.transaction.UserTransaction to mark the beginning and end of the transaction in onMessage(): public class ReservationProcessorBean implements javax.ejb.MessageDrivenBean, javax.jms.MessageListener { MessageDrivenContext ejbContext; Context jndiContext; public void onMessage(Message message) { try { ejbContext.getUserTransaction().begin(); MapMessage reservationMsg = (MapMessage)message; Integer customerPk = (Integer)reservationMsg.getObject("CustomerID"); Integer cruisePk = (Integer)reservationMsg.getObject("CruiseID"); Integer cabinPk = (Integer)reservationMsg.getObject("CabinID"); double price = reservationMsg.getDouble("Price"); //get the credit card Date expirationDate = new Date(reservationMsg.getLong("CreditCardExpDate")); String cardNumber = reservationMsg.getString("CreditCardNum"); String cardType = reservationMsg.getString("CreditCardType"); CreditCardDO card = new CreditCardDO(cardNumber,expirationDate,cardType); CustomerRemote customer = getCustomer(customerPk); CruiseLocal cruise = getCruise(cruisePk); CabinLocal cabin = getCabin(cabinPk); ReservationHomeLocal resHome = (ReservationHomeLocal) jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal"); ReservationLocal reservation = resHome.create(customer,cruise,cabin,price,new Date()); Object ref = jndiContext.lookup("java:comp/env/ejb/ProcessPaymentHomeRemote"); ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) PortableRemoteObject.narrow(ref,ProcessPaymentHomeRemote.class); ProcessPaymentRemote process = ppHome.create(); process.byCredit(customer,card,price); TicketDO ticket = new TicketDO(customer,cruise,cabin,price); deliverTicket(reservationMsg,ticket); ejbContext.getUserTransaction.commit(); } catch(Exception e) { throw new EJBException(e); } } ... It is important to understand that in BMT, the message consumed by the MDB is not part of the transaction. When an MDB uses container-managed transactions, the message it is handling is a part of the transaction, so if the transaction is rolled back, the consumption of the message is also rolled back, forcing the JMS provider to redeliver the message. But with bean-managed transactions, the message is not part of the transaction, so if the BMT transaction is rolled back, the JMS provider will not be aware of the transaction's failure. However, all is not lost, because the JMS provider can still rely on message acknowledgment to determine if the message was successfully delivered. The EJB container will acknowledge the message if the onMessage() method returns successfully. If, however, a RuntimeException is thrown by the onMessage() method, the container will not acknowledge the message and the JMS provider will suspect a problem and will probably attempt to redeliver the message. If redelivery of a message is important when a transaction fails in BMT, your best course of action is to ensure that the onMessage() method throws an EJBException, so that the container will not acknowledge the message received from the JMS provider.
Although the message is not part of the transaction, everything else between the UserTransaction.begin() and UserTransaction.commit() methods is part of the same transaction. This includes creating a new Reservation EJB and processing the credit card using the ProcessPayment EJB. If a transaction failure occurs, these operations will be rolled back. The transaction also includes the use of the JMS API in the deliverTicket() method to send the ticket message. If a transaction failure occurs, the ticket message will not be sent. 14.5.2 Heuristic DecisionsTransactions are normally controlled by a transaction manager (often the EJB server) that manages the ACID characteristics across several enterprise beans, databases, and servers. This transaction manager uses a two-phase commit (2-PC) to manage transactions. 2-PC is a protocol for managing transactions that commits updates in two stages. 2-PC is complex, but basically it requires that servers and databases cooperate through an intermediary, the transaction manager, to ensure that all the data is made durable together. Some EJB servers support 2-PC while others do not, and the value of this transaction mechanism is a source of some debate. The important point to remember is that a transaction manager controls the transaction; based on the results of a poll against the resources (databases, JMS providers, and other resources), it decides whether all the updates should be committed or rolled back. A heuristic decision is when one of the resources makes a unilateral decision to commit or roll back without permission from the transaction manager. Once a heuristic decision has been made, the atomicity of the transaction is lost and data-integrity errors can occur. UserTransaction, discussed in the next section, throws a few different exceptions related to heuristic decisions; these are included in the following discussion. 14.5.3 UserTransactionUserTransaction is a Java interface defined in the following code. EJB servers are not required to support the rest of JTA, nor are they required to use JTS for their transaction service. The UserTransaction is defined as follows: public interface javax.transaction.UserTransaction { public abstract void begin() throws IllegalStateException, SystemException; public abstract void commit() throws IllegalStateException, SystemException, TransactionRolledbackException, HeuristicRollbackException, HeuristicMixedException; public abstract int getStatus(); public abstract void rollback() throws IllegalStateException, SecurityException, SystemException; public abstract void setRollbackOnly() throws IllegalStateException, SystemException; public abstract void setTransactionTimeout(int seconds) throws SystemException; } Here's what the methods defined in this interface do:
14.5.4 StatusStatus is a simple interface that contains no methods, only constants. Its sole purpose is to provide a set of constants that describe the current status of a transactional object—in this case, the UserTransaction: interface javax.transaction.Status { public final static int STATUS_ACTIVE; public final static int STATUS_COMMITTED; public final static int STATUS_COMMITTING; public final static int STATUS_MARKED_ROLLBACK; public final static int STATUS_NO_TRANSACTION; public final static int STATUS_PREPARED; public final static int STATUS_PREPARING; public final static int STATUS_ROLLEDBACK; public final static int STATUS_ROLLING_BACK; public final static int STATUS_UNKNOWN; } The value returned by getStatus() tells the client using the UserTransaction the status of a transaction. Here's what the constants mean:
14.5.5 EJBContext Rollback MethodsOnly BMT beans have access to the UserTransaction from the EJBContext and JNDI ENC. Container-managed transaction (CMT) beans cannot use the UserTransaction. CMT beans use the setRollbackOnly() and getRollbackOnly() methods of the EJBContext to interact with the current transaction instead. The setRollbackOnly() method gives an enterprise bean the power to veto a transaction. This power can be used if the enterprise bean detects a condition that would cause inconsistent data to be committed when the transaction completes. Once an enterprise bean invokes the setRollbackOnly() method, the current transaction is marked for rollback and cannot be committed by any other participant in the transaction—including the container. The getRollbackOnly() method returns true if the current transaction has been marked for rollback. This can be used to avoid executing work that would not be committed anyway. If, for example, an exception is thrown and captured within an enterprise bean method, getRollbackOnly() can be used to determine whether the exception caused the current transaction to be rolled back. If it did, there is no sense in continuing the processing. If it did not, the EJB has an opportunity to correct the problem and retry the task that failed. Only expert EJB developers should attempt to retry tasks within a transaction. Alternatively, if the exception did not cause a rollback (getRollbackOnly() returns false), a rollback can be forced using the setRollbackOnly() method. BMT beans must not use the setRollbackOnly() and getRollbackOnly() methods of the EJBContext. BMT beans should use the getStatus() and rollback() methods on the UserTransaction object to check for rollback and force a rollback, respectively. 14.6 Exceptions and TransactionsExceptions have a large impact on the outcome of transactions and must be discussed in detail so that bean developers understand the relationship between them. 14.6.1 Application Exceptions Versus System ExceptionsAn application exception is any exception that does not extend java.lang.RuntimeException or java.rmi.RemoteException. System exceptions are java.lang.RuntimeException and its subtypes, including EJBException.
Transactions are automatically rolled back if a system exception is thrown from an enterprise bean method. Transactions are not automatically rolled back if an application exception is thrown. If you remember these two rules, you will be well prepared to deal with exceptions and transactions in EJB. The bookPassage() method is a good illustration of an application exception and how it is used. The following sections show the code for the bookPassage() method. 14.6.1.1 EJB 2.0: bookPassage( ) methodpublic TicketDO bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState { if (customer == null || cruise == null || cabin == null) { throw new IncompleteConversationalState(); } try { ReservationHomeLocal resHome = (ReservationHomeLocal) jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal"); ReservationLocal reservation = resHome.create(customer, cruise, cabin, price); Object ref = jndiContext.lookup("java:comp/env/ejb/ProcessPaymentHomeRemote"); ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class); ProcessPaymentRemote process = ppHome.create(); process.byCredit(customer, card, price); TicketDO ticket = new TicketDO(customer,cruise,cabin,price); return ticket; } catch(Exception e) { throw new EJBException(e); } } 14.6.1.2 EJB 1.1: bookPassage( ) methodpublic TicketDO bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState { if (customer == null || cruise == null || cabin == null) { throw new IncompleteConversationalState(); } try { ReservationHomeRemote resHome = (ReservationHomeRemote) getHome("ReservationHomeRemote", ReservationHomeRemote.class); ReservationRemote reservation = resHome.create(customer, cruise, cabin, price); ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) getHome("ProcessPaymentHomeRemote", ProcessPaymentHomeRemote.class); ProcessPaymentRemote process = ppHome.create(); process.byCredit(customer, card, price); TicketDO ticket = new TicketDO(customer,cruise,cabin,price); return ticket; } catch(Exception e) { throw new EJBException(e); } } 14.6.1.3 System exceptionsSystem exceptions are the RuntimeException and its subclasses. The EJBException is a subclass of the RuntimeException, so it is considered a system exception. System exceptions always cause a transaction to roll back when they are thrown from an enterprise bean method. Any RuntimeException (EJBException, NullPointerException, IndexOutOfBoundsException, etc.) thrown within the bookPassage() method is handled by the container automatically and results in a transaction rollback. In Java, RuntimeException types do not need to be declared in the throws clause of the method signature or handled using try/catch blocks; they are automatically thrown from the method. RuntimeException types thrown from within enterprise beans always cause the current transaction to roll back. If the method in which the exception occurs started the transaction, the transaction is rolled back. If the transaction started from a client that invoked the method, the client's transaction is marked for rollback and cannot be committed. System exceptions are handled automatically by the container, which will always:
RuntimeExceptions thrown from the callback methods (ejbLoad(), ejbActivate(), etc.) are treated the same as exceptions thrown from business methods. While EJB requires that system exceptions be logged, it does not specify how exceptions should be logged or the format of the log file. The exact mechanism for recording exceptions and reporting them to the system administrator is left to the vendor. When a system exception occurs, the EJB instance is discarded, which means that it is dereferenced and garbage collected. The container assumes that the EJB instance may have corrupt variables or otherwise be unstable and is therefore unsafe to use. The impact of discarding an EJB instance depends on the enterprise bean's type. In the case of stateless session beans and entity beans, the client does not notice that the instance was discarded. These instance types are not dedicated to a particular client; they are swapped in and out of an instance pool, so any instance can service a new request. With stateful session beans, however, the impact on the client is severe. Stateful session beans are dedicated to a single client and maintain conversational state. Discarding a stateful bean instance destroys the instance's conversational state and invalidates the client's reference to the EJB. When stateful session instances are discarded, subsequent invocations of the EJB's methods by the client result in a NoSuchObjectException, a subclass of the RemoteException.[5] With message-driven beans, a system exception thrown by the onMessage() method or one of the callback methods (ejbCreate() or ejbRemove()) will cause the bean instance to be discarded. If the MDB was BMT bean, the message it was handling may or may not be redelivered, depending on when the EJB container acknowledges delivery. In the case of container-managed transactions, the container will roll back the transaction, so the message will not be acknowledged and may be redelivered. In session and entity beans, when a system exception occurs and the instance is discarded, a RemoteException is always thrown to remote clients; that is, clients using the beans' remote component interfaces. If the client started the transaction, which was then propagated to the EJB, a system exception (thrown by the enterprise bean method) will be caught by the container and rethrown as a javax.transaction.TransactionRolledbackException. The TransactionRolledbackException is a subtype of the RemoteException; it is a more explicit indication to the client that a rollback occurred. In EJB 2.0 session and entity beans, when a system exception occurs and the instance is discarded, an EJBException is always thrown to any local enterprise bean clients (i.e., clients using the enterprise bean's local component interfaces). If the client started the transaction and it was then propagated to the EJB, a system exception (thrown by the enterprise bean method) will be caught by the container and rethrown as a javax.ejb.TransactionRolledbackLocalException. The TransactionRolledbackLocalException is a subtype of the EJBException; it is a more explicit indication to the client that a rollback occurred. In all other cases, whether the EJB is container-managed or bean-managed, a RuntimeException thrown from within the enterprise bean method will be caught by the container and rethrown as an EJBException. An EJBException should generally be thrown when a subsystem throws an exception, such as JDBC throwing a SQLException or JMS throwing a JMSException. In some cases, however, the bean developer may attempt to handle the exception and retry an operation rather then throw an EJBException. This should be done only when the exceptions thrown by the subsystem and their repercussions on the transaction are well understood. As a rule of thumb, rethrow subsystem exceptions as EJBExceptions and allow the EJB container to roll back the transaction and discard the bean instance.
14.6.1.4 Application exceptionsAn application exception is normally thrown in response to a business-logic error, as opposed to a system error. Application exceptions are always delivered directly to the client, without being repackaged as RemoteException or EJBException (EJB 2.0) types. They do not typically cause transactions to roll back; the client usually has an opportunity to recover after an application exception is thrown. For example, the bookPassage() method throws an application exception called IncompleteConversationalState; this is an application exception because it does not extend RuntimeException or RemoteException. The IncompleteConversationalState exception is thrown if one of the arguments passed into the bookPassage() method is null. (Application errors are frequently used to report validation errors like this.) In this case, the exception is thrown before tasks are started and is clearly not the result of a subsystem (e.g., JDBC, JMS, Java RMI, JNDI) failure. Because it is an application exception, throwing an IncompleteConversationalState exception does not result in a transaction rollback. The exception is thrown before any work is done, avoiding unnecessary processing by the bookPassage() method and providing the client (the enterprise bean or application that invoked the bookPassage() method) with an opportunity to recover and possibly retry the method call with valid arguments. Business methods defined in the remote and local interfaces can throw any kind of application exception. These application exceptions must be declared in the method signatures of the remote and local interfaces and in the corresponding methods in the enterprise EJB classes. The EJB create, find, and remove methods can also throw several exceptions defined in the javax.ejb package: CreateException, DuplicateKeyException, FinderException, ObjectNotFoundException, and RemoveException. These exceptions are considered application exceptions: they are delivered to the client as-is, without being repackaged as RemoteExceptions. Furthermore, these exceptions don't necessarily cause a transaction to roll back, giving the client the opportunity to retry the operation. These exceptions may be thrown by the EJBs themselves; in the case of container-managed persistence, the container can also throw any of these exceptions while handling the EJB's create, find, or remove methods (ejbCreate(), ejbFind(), and ejbRemove()). The container might, for example, throw a CreateException if it encounters a bad argument while attempting to insert a record for a container-managed EJB. You can always choose to throw a standard application exception from the appropriate method regardless of how persistence is managed. Here is a detailed explanation of the five standard application exceptions and the situations in which they are thrown:
Table 14-1 summarizes the interactions between different types of exceptions and transactions in session and entity beans.
Table 14-2 summarizes the interactions between different types of exceptions and transactions in message-driven beans.
14.7 Transactional Stateful Session BeansAs you saw in Chapter 12, session beans can interact directly with the database as easily as they can manage the workflow of other enterprise beans. The ProcessPayment EJB, for example, makes inserts into the PAYMENT table when the byCredit() method is invoked, and the TravelAgent EJB queries the database directly when the listAvailableCabins() method is invoked. Stateless session beans such as the ProcessPayment EJB have no conversational state, so each method invocation must make changes to the database immediately. With stateful session beans, however, we may not want to make changes to the database until the transaction is complete. Remember, a stateful session bean can be just one participant out of many in a transaction, so it may be advisable to postpone database updates until the entire transaction is committed or to avoid updates if it is rolled back. There are several different scenarios in which a stateful session bean would want to cache changes before applying them to the database. For example, think of a shopping cart implemented by a stateful session bean that accumulates several items for purchase. If the stateful bean implements SessionSynchronization, it can cache the items and write them to the database only when the transaction is complete. The javax.ejb.SessionSynchronization interface allows a session bean to receive additional notification of the session's involvement in transactions. The addition of these transaction callback methods by the SessionSynchronization interface expands the EJB's awareness of its life cycle to include a new state, the Transactional Method-Ready state. This third state, although not discussed in Chapter 12, is always a part of the life cycle of a transactional stateful session bean. Implementing the SessionSynchronization interface simply makes it visible to the EJB. Figure 14-11 shows the stateful session bean with the additional state. Figure 14-11. Life cycle of a stateful session bean
The SessionSynchronization interface is defined as follows: package javax.ejb; public interface javax.ejb.SessionSynchronization { public abstract void afterBegin() throws RemoteException; public abstract void beforeCompletion() throws RemoteException; public abstract void afterCompletion(boolean committed) throws RemoteException; } When a method of the SessionSynchronization bean is invoked outside of a transaction scope, the method executes in the Method-Ready state, as discussed in Chapter 12. However, when a method is invoked within a transaction scope (or creates a new transaction), the EJB moves into the Transactional Method-Ready state. 14.7.1 The Transactional Method-Ready StateThe SessionSynchronization methods are called in the Transactional Method-Ready state. 14.7.1.1 Transitioning into the Transactional Method-Ready stateWhen a transactional method is invoked on a SessionSynchronization bean, the stateful bean becomes part of the transaction. This causes the afterBegin() callback method defined in the SessionSynchronization interface to be invoked. This method should take care of reading any data from the database and storing the data in the bean's instance fields. The afterBegin() method is called before the EJB object delegates the business-method invocation to the EJB instance. 14.7.1.2 Life in the Transactional Method-Ready stateWhen the afterBegin() callback method completes, the business method originally invoked by the client is executed on the EJB instance. Any subsequent business methods invoked within the same transaction will be delegated directly to the EJB instance. Once a stateful session bean is a part of a transaction—whether it implements SessionSynchronization or not—it cannot be accessed by any other transactional context. This is true regardless of whether the client tries to access the EJB with a different context or the EJB's own method creates a new context. If, for example, a method with a transaction attribute of RequiresNew is invoked, the new transactional context causes an error to be thrown. Since the NotSupported and Never attributes specify a different transactional context (no context), invoking a method with these attributes also causes an error. A stateful session bean cannot be removed while it is involved in a transaction. This means that invoking ejbRemove() while the SessionSynchronization bean is in the middle of a transaction will cause an error to be thrown. At some point, the transaction in which the SessionSynchronization bean has been enrolled will come to an end. If the transaction is committed, the SessionSynchronization bean will be notified through its beforeCompletion() method. At this time, the EJB should write its cached data to the database. If the transaction is rolled back, the beforeCompletion() method will not be invoked, avoiding the pointless effort of writing changes that won't be committed to the database. The afterCompletion() method is always invoked, whether the transaction ended successfully with a commit or unsuccessfully with a rollback. If the transaction was a success—which means that beforeCompletion() was invoked—the committed parameter of the afterCompletion() method will be true. If the transaction was unsuccessful, committed will be false. It may be desirable to reset the stateful session bean's instance variables to some initial state if the afterCompletion() method indicates that the transaction was rolled back.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|